import struct
import serial
import threading
import logging
import time

# 初始化日志配置
logging.basicConfig(filename='serial_test_tool.log', level=logging.INFO, format='%(asctime)s - %(message)s')

# 数据包同步码（0xAA55）
SYNC_CODE = 0x55AA

# 丢包统计变量
total_packets = 0
lost_packets = 0

# CRC 计算函数
def calc_crc(data):
    return sum(data) & 0xFFFF  # 计算 16 位累加和

# 编码函数：将数据打包成符合 pkt_t 结构体的数据包（小端序）
def packet_encode(seq, cmd, data):
    header = SYNC_CODE
    data_len = len(data)
    total_len = 12 + data_len  # 12 是固定头部的字节数
    crc = 0  # CRC 暂时为 0，稍后计算
    
    # 打包头部和数据，使用小端序
    packet = struct.pack(f'<HHHHHH{data_len}s', header, total_len, crc, seq, cmd, data_len, data)
    
    # 计算 CRC，排除 CRC 字段本身
    crc = calc_crc(packet[0:4] + packet[6:])
    
    # 更新 CRC 字段，重新打包数据
    packet = struct.pack(f'<HHHHHH{data_len}s', header, total_len, crc, seq, cmd, data_len, data)
    
    return packet

# 解码函数：从字节流中提取数据（小端序）
def packet_decode(packet):
    if len(packet) < 12:
        raise ValueError("Incomplete header")

    # 解码固定头部，使用小端序
    header, total_len, crc, seq, cmd, data_len = struct.unpack('<HHHHHH', packet[:12])
    
    # 检查接收到的数据是否足够解码
    if total_len != 12 + data_len or len(packet) < total_len:
        raise ValueError(f"Incomplete packet. Expected {total_len} bytes, got {len(packet)} bytes.")

    # 解码数据部分，并将数据转换为十六进制大写输出
    data = struct.unpack(f'<{data_len}s', packet[12:12+data_len])[0].hex().upper()
    
    # 计算 CRC 进行校验
    calculated_crc = calc_crc(packet[0:4] + packet[6:])
    
    if crc != calculated_crc:
        raise ValueError("CRC 校验失败")
    
    return {
        'header': header,
        'total_len': total_len,
        'crc': crc,
        'seq': seq,
        'cmd': cmd,
        'data_len': data_len,
        'data': data  # 以16进制大写形式输出
    }

# 实时计算丢包率函数
def calculate_packet_loss_rate(total, lost):
    if total == 0:
        return 0
    return (lost / total) * 100

# 串口接收线程函数，确保以 0xAA55 开头的数据帧完整输出
def serial_receive(serial_port, stop_event, seq_tracker):
    global total_packets, lost_packets  # 使用全局变量进行丢包统计
    buffer = b''  # 缓存区
    while not stop_event.is_set():
        if serial_port.in_waiting > 0:
            buffer += serial_port.read(serial_port.in_waiting)

            # 查找以0xAA55为开头的完整帧
            while len(buffer) >= 12:
                header = struct.unpack('<H', buffer[:2])[0]

                # 找到以AA55开头的同步码
                if header == SYNC_CODE:
                    # 提取总长度字段
                    total_len = struct.unpack('<H', buffer[2:4])[0]
                    
                    # 检查是否有足够的数据来解析完整的数据包
                    if len(buffer) >= total_len:
                        packet = buffer[:total_len]
                        buffer = buffer[total_len:]  # 移除已处理的部分

                        # 打印原始数据以十六进制大写
                        hex_data = packet.hex().upper()
                        print(f"Raw Data: {hex_data}")
                        logging.info(f"Raw Data: {hex_data}")

                        try:
                            decoded_packet = packet_decode(packet)
                            print(f"Decoded packet: {decoded_packet}")
                            logging.info(f"Decoded packet: {decoded_packet}")

                            # 统计总包数
                            total_packets += 1

                            # 检测丢包现象
                            if seq_tracker['last_seq'] is not None and decoded_packet['seq'] != seq_tracker['last_seq'] + 1:
                                lost_packets += (decoded_packet['seq'] - seq_tracker['last_seq'] - 1)
                                print(f"Warning: Packet loss detected. Expected seq {seq_tracker['last_seq'] + 1}, got {decoded_packet['seq']}")
                                logging.warning(f"Packet loss detected. Expected seq {seq_tracker['last_seq'] + 1}, got {decoded_packet['seq']}")
                            
                            seq_tracker['last_seq'] = decoded_packet['seq']  # 更新序列号追踪器

                            # 实时计算并显示丢包率
                            loss_rate = calculate_packet_loss_rate(total_packets, lost_packets)
                            print(f"Total Packets: {total_packets}, Lost Packets: {lost_packets}, Loss Rate: {loss_rate:.2f}%")

                        except ValueError as e:
                            print(f"Error: {e}")
                            logging.error(f"Error: {e}")
                    else:
                        # 数据不足以组成完整帧，等待更多数据
                        break
                else:
                    # 如果找不到同步码，丢弃前面的数据继续搜索
                    buffer = buffer[1:]

# 串口发送函数
def serial_send(serial_port, seq, cmd, data):
    encoded_packet = packet_encode(seq, cmd, data)
    serial_port.write(encoded_packet)
    hex_data = encoded_packet.hex().upper()
    print(f"Sent: {hex_data}")
    logging.info(f"Sent: {hex_data}")

# 设置串口参数
def setup_serial(port, baudrate=115200, timeout=5):  # 将超时设置为5秒
    return serial.Serial(port, baudrate, timeout=timeout)

# 命令行用户输入
def command_interface(serial_port, seq_tracker):
    seq = seq_tracker['last_seq'] + 1 if seq_tracker['last_seq'] is not None else 1
    while True:
        cmd = input("Enter command (or 'exit' to quit): ").strip()
        if cmd == 'exit':
            break
        elif cmd == 'send':  # 示例：发送命令
            data = b'hello world'
            serial_send(serial_port, seq, 0x10, data)
            seq += 1  # 每次发送后递增序列号
            seq_tracker['last_seq'] = seq
        else:
            print(f"Unknown command: {cmd}")

if __name__ == "__main__":
    # 设置串口
    serial_port = setup_serial('/dev/tty.usbmodem14201', 115200)

    # 创建一个事件来停止接收线程
    stop_event = threading.Event()

    # 序列号追踪器
    seq_tracker = {'last_seq': None}

    # 启动串口接收线程
    receive_thread = threading.Thread(target=serial_receive, args=(serial_port, stop_event, seq_tracker))
    receive_thread.start()

    # 启动命令行接口，供用户输入命令
    try:
        command_interface(serial_port, seq_tracker)
    except KeyboardInterrupt:
        pass

    # 停止接收线程
    stop_event.set()
    receive_thread.join()

    # 关闭串口
    serial_port.close()
    print("Program terminated.")
